var mysql = require('mysql');

module.exports = new(function (config) {
    this.config = config || sysConfig.mysql;

    this.new = function (config) {
        return Object.assign({}, this, {
            config: config
        });
    };

    this.execMapper = function (method, args, callback) {
        if (typeof args == 'function') {
            callback = args;
            args = null;
        }
        exec(this.config, parseSql(method(args), args), args, callback);
    };

    this.execMapperSync = function (method, args) {
        return execSync(this.config, parseSql(method(args), args), args);
    };

    this.exec = function (sql, args, callback) {
        if (typeof args == 'function') {
            callback = args;
            args = null;
        }
        exec(this.config, parseSql(sql, args), args, callback);
    };

    this.execSync = function (sql, args) {
        return execSync(this.config, parseSql(sql, args), args);
    };

    this.execTransaction = function (sql, args, callback) {
        if (typeof args == 'function') {
            callback = args;
            args = null;
        }
        execTransaction(this.config, parseSql(sql, args), args, callback);
    };

    this.execTransactionSync = function (sql, args) {
        return execTransactionSync(this.config, parseSql(sql, args), args);
    };

    this.escape = mysql.escape;

    return this;
})();

function parseSql(sql, args) {
    if (args) {
        var reg = /\{([^\{\}]+?)\}/g;
        var ms = sql.match(reg);
        if (ms != null) {
            for (var i = 0; i < ms.length; i++) {
                var mkeys = ms[i].substring(1, ms[i].length - 1).split('||');
                if (!args[mkeys[0].trim()] || args[mkeys[0].trim()].toStr() == '') {
                    if (mkeys.length == 1) {
                        sql = sql.replace(ms[i], '\'\'');
                    } else {
                        sql = sql.replace(ms[i], '');
                    }
                } else if (mkeys.length == 1) {
                    sql = sql.replace(ms[i], mysql.escape(args[mkeys[0].trim()].toStr()).replace(/#/g, '＃'));
                } else {
                    sql = sql.replace(ms[i], mkeys[1].trim());
                }
            }
        }

        reg = /#([^\s'%"!=,]+)/g;
        ms = sql.match(reg);
        if (ms == null) {
            return sql.replace(/＃/g, '#');
        }
        for (var i = 0; i < ms.length; i++) {
            var key = ms[i].substring(1).trim();
            if (!args[key] || args[key].toStr() == '') {
                sql = sql.replace(ms[i], '');
            } else {
                var v = mysql.escape(args[key].toStr());
                sql = sql.replace(ms[i], v.substr(1, v.length - 2).replace(/#/g, '＃'));
            }
        }
        return sql.replace(/＃/g, '#');
    } else {
        return sql.replace(/＃/g, '#');
    }
}

function getPool(config) {
    var key = config.host + config.database;
    if (!global.mysqlPool) {
        global.mysqlPool = Object.create(null);
    }
    if (!global.mysqlPool[key]) {
        global.mysqlPool[key] = mysql.createPool(config);
    }
    return global.mysqlPool[key];
}

function exec(config, sql, args, callback) {
    getPool(config).getConnection(function (err, conn) {
        if (err) {
            logger.error(err);
            callback && callback(err, null, null, args);
            return;
        }

        conn.query({
            sql: sql,
            timeout: config.timeout
        }, function (err, rows, fields) {
            releaseConnection(conn);

            if (err) {
                logger.error(err);
                callback && callback(err, null, null, args);
                return;
            }

            callback && callback(null, rows, fields, args);
        });
    });
}

function execSync(config, sql, args) {
    return new Promise((resolve) => {
        getPool(config).getConnection(function (err, conn) {
            if (err) {
                logger.error(err);
                resolve({
                    err: err,
                    rows: null,
                    fields: null,
                    args: args
                });
                return;
            }

            conn.query({
                sql: sql,
                timeout: config.timeout
            }, function (err, rows, fields) {
                releaseConnection(conn);

                if (err) {
                    logger.error(err);
                    resolve({
                        err: err,
                        rows: null,
                        fields: null,
                        args: args
                    });
                    return;
                }

                resolve({
                    err: err,
                    rows: rows,
                    fields: fields,
                    args: args
                });
            });
        });
    });
}

function execTransaction(config, sql, args, callback) {
    getPool(config).getConnection(function (err, conn) {
        if (err) {
            logger.error(err);
            callback && callback(err, null, null, args);
            return;
        }

        conn.beginTransaction(function (err) {
            if (err) {
                logger.error(err);
                releaseConnection(conn);
                callback && callback(err, null, null, args);
                return;
            }
            conn.query({
                sql: sql,
                timeout: config.timeout
            }, function (err, rows, fields) {
                if (err) {
                    logger.error(err);
                    conn.rollback(function () {
                        releaseConnection(conn);
                        callback && callback(err, null, null, args);
                    });
                    return;
                }
                conn.commit(function (err) {
                    if (err) {
                        logger.error(err);
                        conn.rollback(function () {
                            releaseConnection(conn);
                            callback && callback(err, null, null, args);
                        });
                        return;
                    }

                    releaseConnection(conn);
                    callback && callback(null, rows, fields, args);
                });
            });
        });
    });
}

function execTransactionSync(config, sql, args) {
    return new Promise((resolve) => {
        getPool(config).getConnection(function (err, conn) {
            if (err) {
                logger.error(err);
                resolve({
                    err: err,
                    rows: null,
                    fields: null,
                    args: args
                });
                return;
            }

            conn.beginTransaction(function (err) {
                if (err) {
                    logger.error(err);
                    releaseConnection(conn);
                    resolve({
                        err: err,
                        rows: null,
                        fields: null,
                        args: args
                    });
                    return;
                }
                conn.query({
                    sql: sql,
                    timeout: config.timeout
                }, function (err, rows, fields) {
                    if (err) {
                        logger.error(err);
                        conn.rollback(function () {
                            releaseConnection(conn);
                            resolve({
                                err: err,
                                rows: null,
                                fields: null,
                                args: args
                            });
                        });
                        return;
                    }
                    conn.commit(function (err) {
                        if (err) {
                            logger.error(err);
                            conn.rollback(function () {
                                releaseConnection(conn);
                                resolve({
                                    err: err,
                                    rows: null,
                                    fields: null,
                                    args: args
                                });
                            });
                            return;
                        }

                        releaseConnection(conn);
                        resolve({
                            err: err,
                            rows: rows,
                            fields: fields,
                            args: args
                        });
                    });
                });
            });
        });
    });
}

function releaseConnection(conn) {
    if (conn)
        conn.release();
}